
// Copyright (c) 2010-2021 niXman (github dot nixman at pm dot me). All
// rights reserved.
//
// This file is part of YAS(https://github.com/niXman/yas) project.
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//
//
// Boost Software License - Version 1.0 - August 17th, 2003
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef __yas__detail__tools__json_tools_hpp
#define __yas__detail__tools__json_tools_hpp

#include <yas/detail/io/serialization_exceptions.hpp>

#include <cstdint>

namespace yas {
namespace detail {

/***************************************************************************/

template<typename Archive>
void json_skipws(Archive &ar) {
    while ( true ) {
        const char ch = ar.peekch();
        switch ( ch ) {
            case ' ':
            case '\n':
            case '\t':
            case '\r': { ar.getch(); break; }
            default  : return;
        }
    }
}

/***************************************************************************/

template<typename Archive>
void json_skip_num(Archive &ar) {
    while ( true ) {
        const char ch = ar.peekch();
        switch ( ch ) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '.':
            case 'e':
            case 'E': { ar.getch(); break; }
            default : return;
        }
    }
}

/***************************************************************************/

template<typename Archive>
void json_skip_unicode(Archive &ar) {
    char buf[4];
    ar.read(buf, 4);
}

/***************************************************************************/

template<typename Archive>
void json_skip_bool(char ch, Archive &ar) {
    char buf[4];
    if ( ch == 't' ) {
        ar.read(buf, 3);
    } else {
        ar.read(buf, 4);
    }
}

/***************************************************************************/

template<typename Archive>
void json_skip_null(Archive &ar) {
    char buf[3];
    ar.read(buf, 3);
}

/***************************************************************************/

template<typename Archive>
void json_skip_escapes(Archive &ar) {
    const char ch = ar.peekch();
    switch ( ch ) {
        case '\"':
        case '\\':
        case '/' :
        case 'b' :
        case 'f' :
        case 'n' :
        case 'r' :
        case 't' :
        default  : { ar.getch(); break; }
        case 'u' : return json_skip_unicode(ar);
    }
}

/***************************************************************************/

template<typename Archive>
void json_skip_string(Archive &ar) {
    while ( true ) {
        const char ch = ar.getch();
        switch ( ch ) {
            case '\"': return;
            case '\\': {
                const char ch2 = ar.getch();
                switch ( ch2 ) {
                    case '\"':
                    case '\\':
                    case '/' :
                    case 'b' :
                    case 'f' :
                    case 'n' :
                    case 'r' :
                    case 't' : return;
                    case 'u' : return json_skip_unicode(ar);
                    default: __YAS_THROW_INVALID_JSON_STRING("invalid string: forbidden char")
                }
            }
            default: continue;
        }
    }
}

/***************************************************************************/

template<typename Archive>
void json_skip_array(Archive &ar) {
    while ( true ) {
        json_skipws(ar);
        json_skip_val(ar);

        json_skipws(ar);
        const char ch = ar.getch();
        if ( ch == ']' ) break;
    }
}

/***************************************************************************/

template<typename Archive>
void json_skip_object(Archive &ar) {
    while ( true ) {
        json_skipws(ar);
        __YAS_THROW_IF_BAD_JSON_CHARS(ar, "\"")

        json_skip_string(ar); // key

        json_skipws(ar);
        __YAS_THROW_IF_BAD_JSON_CHARS(ar, ":")

        json_skipws(ar);
        json_skip_val(ar); // val

        json_skipws(ar);
        const char ch = ar.getch();
        if ( ch == '}' ) break;
    }
}

/***************************************************************************/

template<typename Archive>
void json_skip_val(Archive &ar) {
    const char ch = ar.getch();
    switch ( ch ) {
        case '\"': { json_skip_string(ar); break; }
        case 't' : { json_skip_bool('t', ar); break; }
        case 'f' : { json_skip_bool('f', ar); break; }
        case 'n' : { json_skip_null(ar); break; }
        case '[' : { json_skip_array(ar); break; }
        case '{' : { json_skip_object(ar); break; }
        case '-' :
        case '0' :
        case '1' :
        case '2' :
        case '3' :
        case '4' :
        case '5' :
        case '6' :
        case '7' :
        case '8' :
        case '9' : { json_skip_num(ar); break; }
        default  : break;
    }
}

/***************************************************************************/

template<typename Archive>
std::size_t json_read_key(Archive &ar, char *ptr, std::size_t size) {
    const char *p = ptr;
    __YAS_CONSTEXPR_IF( Archive::flags() & yas::compacted ) {
        while ( size-- ) {
            *ptr = ar.getch();
            if ( *ptr == '\"' ) {
                ar.ungetch(*ptr);
                *ptr = 0;

                return ptr-p;
            }
            ++ptr;
        }
    }

    while ( size-- ) {
        *ptr = ar.getch();
        if ( *ptr == '\"' ) {
            continue;
        } else if ( *ptr == ':' ) {
            break;
        }
        ++ptr;
    }
    *ptr = 0;

    return ptr-p;
}

/***************************************************************************/

template<typename Archive>
bool is_valid_for_int_and_double(Archive &, char ch) {
    switch ( ch ) {
        case '0':
        case '1':
        case '2':
        case '3':
        case '4':
        case '5':
        case '6':
        case '7':
        case '8':
        case '9':
        case '-':
        case '.':
        case 'e':
        case 'E': return true;
        default : return false;
    }
}

template<typename Archive>
std::size_t json_read_num(Archive &ar, char *ptr, std::size_t size) {
    char *p = ptr;
    do {
        *ptr = ar.getch();
        switch ( *ptr ) {
            case '-':
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9': break;
            default : {
                ar.ungetch(*ptr);
                return ptr-p;
            }
        }
        ++ptr;
    } while ( --size );

    return ptr-p;
}

template<typename Archive>
std::size_t json_read_double(Archive &ar, char *ptr, std::size_t size) {
    char *p = ptr;
    do {
        *ptr = ar.getch();
        switch ( *ptr ) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
            case '-':
            case '.':
            case 'e':
            case 'E': break;
            default : {
                ar.ungetch(*ptr);
                return ptr-p;
            }
        }
        ++ptr;
    } while ( --size );

    return ptr-p;
}

/***************************************************************************/

} // ns detail
} // ns yas

#endif // __yas__detail__tools__json_tools_hpp
